<?php

class DB {
    private static $_instance;
    protected $statements = array(
        'selects' => array(),
        'froms' => array(),
        'wheres' => array(),
        'limit' => FALSE,
        'offset' => FALSE
    );
    private $conn;
    private $sql;
    private $query;

    private function __construct() {
        $this->_connect(CONFIG::get('database'));
    }

    function __destruct() {
        $this->conn->close();
    }

    public static function getInstance() {
        if (!(self::$_instance instanceof self)) {
            self::$_instance = new self;
        }
        return self::$_instance;
    }

    private function _connect($config) {
        if (!empty($this->conn)) {
            return TRUE;
        }
        $this->conn = new mysqli($config['host'], $config['username'], $config['password'], $config['database']);
        if (!$this->conn) {
            die('Could not connect: ' . mysql_error());
        }

        $this->conn->set_charset($config['charset']);
        
        return TRUE;
    }

    public function insert($table, $data) {
        if (empty($table)) {
            echo "No table set!";
            return FALSE;
        }
        $sql = "INSERT INTO $table";
        $sql .= "(".implode(",", array_keys($data)).")";
        # Add protect
        foreach ($data as &$value) {
            $value = "'".addslashes($value)."'";
        }
        $sql .= " VALUES (".implode(",", array_values($data)).")";
        if (!$this->conn->query($sql)) {
            $this->display_error($sql);
            return FALSE;
        }
        $this->reset_statements();
        return TRUE;
    }
    public function update($table, $set_array) {
        foreach ($set_array as $key => $val)
        {
            $valstr[] = "`$key`"." = '".$val."'";
        }
        $sql = "UPDATE ".$table." SET ".implode(', ', $valstr);

        $sql .= " WHERE 1 AND";
        foreach ($this->statements['wheres'] as $where) {
            $sql .= " `".$where['key'] ."`" . $where['operator'] . $where['value']." ". $where['joiner'];
        }
        $sql = substr($sql,0, strrpos($sql, ' '));
        
        $this->reset_statements();
        return $this->conn->query($sql);
    }
    public function delete($table) {
        $sql = "DELETE FROM ".$table;

        $sql .= " WHERE 1 AND";
        foreach ($this->statements['wheres'] as $where) {
            $sql .= " `".$where['key'] ."`" . $where['operator'] . $where['value']." ". $where['joiner'];
        }
        $sql = substr($sql,0, strrpos($sql, ' '));
        
        $this->reset_statements();
        return $this->conn->query($sql);
    }
    public function increase($table, $key_array, $value=1) {
        foreach ($key_array as $key)
        {
            $valstr[] = "`$key`"." = `".$key."`+$value";
        }
        $sql = "UPDATE ".$table." SET ".implode(', ', $valstr);

        $sql .= " WHERE 1 AND";
        foreach ($this->statements['wheres'] as $where) {
            $sql .= " `".$where['key'] ."`" . $where['operator'] . $where['value']." ". $where['joiner'];
        }
        $sql = substr($sql,0, strrpos($sql, ' '));
        
        $this->reset_statements();
        return $this->conn->query($sql);
    }
    public function where($key, $operator = null, $value = null) {
        if (func_num_args() == 2) {
            $value = $operator;
            $operator = '=';
        }
        $this->_where($key, $operator, $value);
    }
    public function where_like($key, $operator = "like", $value = null) {
        if (func_num_args() == 2) {
            $value = $operator;
            $operator = 'like';
        }
        $value = "%$value%";
        $this->_where($key, $operator, $value);
    }
    public function select($fields) {
        $this->addStatement('selects', $fields);
        return $this;
    }

    public function from($tables) {
        $this->addStatement('froms', $tables);
    }
    public function limit($limit, $offset=FALSE) {
        $this->statements['limit']= (int) $limit;

        if ($offset != FALSE)
        {
            $this->statements['offset'] = (int) $offset;
        }
    }
    public function get($table) {
        $sql = "select ";
        $select_string = "";
        $this->from($table);
        $sql = $this->_compile_select();
        $this->query = $this->conn->query($sql);
        if (!$this->query) {
            $this->display_error($sql);
        }
        $this->reset_statements();
    }
    public function reset_statements() {
        foreach ($this->statements as &$statement) {
            if (is_array($statement)) {
                $statement = array();
            } else if(is_bool($statement)) {
                $statement = FALSE;
            }
        }
    }
    public function fetch_all() {
        if ($this->query) {
            $result = $this->query->fetch_all(MYSQLI_ASSOC);
            $this->free_query();
            return $result;
        } else {
            return array();
        }
    }
    public function fetch_row() {
        $row = $this->query->fetch_assoc();
        return $row;
    }
    public function free_query() {
        mysqli_free_result($this->query);
    }
    protected function _where($key, $operator = null, $value = null, $joiner = 'AND') {
        $this->statements['wheres'][] = compact('key', 'operator', 'value', 'joiner');
    }

    protected function addStatement($key, $value) {
        if (!is_array($value)) {
            $value = array($value);
        }

        if (!array_key_exists($key, $this->statements)) {
            $this->statements[$key] = $value;
        } else {
            $this->statements[$key] = array_merge($this->statements[$key], $value);
        }
    }

    protected function _compile_select() {
        $sql = "SELECT ";
        if (count($this->statements['selects']) == 0) {
            $sql .= '*';
        } else {
            // Cycle through the "select" portion of the query and prep each column name.
            // The reason we protect identifiers here rather then in the select() function
            // is because until the user calls the from() function we don't know if there are aliases

            $sql .= implode(', ', $this->statements['selects']);
        }
        $sql .= " FROM ";
        $sql .= implode(', ', $this->statements['froms']);
        $sql .= " WHERE 1 AND";
        foreach ($this->statements['wheres'] as $where) {
            $sql .= " `".$where['key'] ."`" . $where['operator'] ." '". $where['value']."' ". $where['joiner'];
        }
        $sql = substr($sql,0, strrpos($sql, ' '));
        // Output limit statements
        if (is_numeric($this->statements['limit'])) {
            if (is_numeric($this->statements['offset'])) {
                $sql .= " limit ".$this->statements['offset'].", ".$this->statements['limit'];
            } else {
                $sql .= " limit ". $this->statements['limit'];
            }
        }
        return $sql;
    }

    private function display_error($sql="") {
        printf("Mysql error: %s\n, sql:%s", $this->conn->error, $sql);
    }
}
